package main

import (
	"context"
	"flag"
	"fmt"
	"io/fs"
	"os"
	"path/filepath"
	"sigs.k8s.io/yaml"
	"sort"
	"strings"

	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	rbacv1 "k8s.io/api/rbac/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/clientcmd"
)

// ----------------------------- CLI FLAGS -----------------------------

// 定义命令行参数
var (
	flagNamespaces multiString // 命名空间列表，支持多个值
	flagKubeconfig string      // kubeconfig文件路径
	flagRoot       string      // 输出根目录
	flagConfigPath string      // 字段同步配置文件路径
)

// multiString类型用于支持多个值的命令行参数
type multiString []string

// String方法返回multiString的字符串表示
func (m *multiString) String() string { return strings.Join(*m, ",") }

// Set方法用于解析命令行参数到multiString
func (m *multiString) Set(s string) error {
	if s == "" {
		return nil
	}
	*m = append(*m, s)
	return nil
}

// ----------------------------- CONFIG -----------------------------

// FieldRule定义字段过滤规则，包括包含和排除的字段列表
type FieldRule struct {
	Include []string `yaml:"include" json:"include"` // 包含的字段路径
	Exclude []string `yaml:"exclude" json:"exclude"` // 排除的字段路径
}

// Config定义全局配置，包含各资源类型的字段过滤规则
type Config struct {
	Deployment FieldRule `yaml:"deployment" json:"deployment"` // Deployment资源过滤规则
	Service    FieldRule `yaml:"service" json:"service"`       // Service资源过滤规则
	ConfigMap  FieldRule `yaml:"configmap" json:"configmap"`   // ConfigMap资源过滤规则
	RBAC       FieldRule `yaml:"rbac" json:"rbac"`             // RBAC资源过滤规则
}

// loadConfig从指定路径加载配置文件，若路径为空则返回默认配置
func loadConfig(path string) (*Config, error) {
	if path == "" {
		return defaultConfig(), nil
	}
	b, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}
	cfg := defaultConfig()
	if err := yaml.Unmarshal(b, cfg); err != nil {
		return nil, err
	}
	return cfg, nil
}

// defaultConfig返回默认的字段过滤配置
func defaultConfig() *Config {
	return &Config{
		Deployment: FieldRule{
			Include: nil,
			Exclude: []string{
				"status",
				"metadata.annotations",
				"metadata.managedFields",
				"metadata.creationTimestamp",
				"metadata.generation",
				"metadata.resourceVersion",
				"metadata.uid",
				"metadata.labels.*",
				"spec.template.metadata.creationTimestamp",
				"spec.template.metadata.labels.*",
				"spec.template.spec.containers[*].terminationMessagePath",
				"spec.template.spec.containers[*].terminationMessagePolicy",
				"spec.template.spec.restartPolicy",
				"spec.template.spec.terminationGracePeriodSeconds",
				"spec.template.spec.dnsPolicy",
				"spec.template.spec.securityContext",
				"spec.template.spec.schedulerName",
			},
		},
		Service: FieldRule{
			Include: []string{
				"apiVersion", "kind",
				"metadata.name", "metadata.namespace", "metadata.labels",
				"spec.ports", "spec.selector", "spec.type",
			},
		},
		ConfigMap: FieldRule{
			Include: []string{
				"apiVersion", "kind",
				"metadata.name", "metadata.namespace", "metadata.labels",
				"data",
			},
		},
		RBAC: FieldRule{}, // RBAC资源默认不过滤
	}
}

// ----------------------------- K8S CLIENT -----------------------------

// mustClient初始化Kubernetes客户端，若失败则panic
func mustClient(kubeconfig string) *kubernetes.Clientset {
	restCfg, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
	if err != nil {
		panic(fmt.Errorf("load kubeconfig failed: %w", err))
	}
	cli, err := kubernetes.NewForConfig(restCfg)
	if err != nil {
		panic(fmt.Errorf("new client failed: %w", err))
	}
	return cli
}

// ----------------------------- OBJECT -> MAP -> YAML -----------------------------

// ----------------------------- FIELD FILTERING -----------------------------

// getNested根据点分路径从map中获取嵌套值
func getNested(m map[string]any, path string) (map[string]any, string, bool) {
	n := strings.Split(path, ".")
	cur := m
	for i := 0; i < len(n)-1; i++ {
		v, ok := cur[n[i]]
		if !ok {
			return nil, "", false
		}
		mm, ok := v.(map[string]any)
		if !ok {
			return nil, "", false
		}
		cur = mm
	}
	return cur, n[len(n)-1], true
}

// deletePath根据点分路径删除map中的字段
func deletePath(m map[string]any, path string) {
	segs := strings.Split(path, ".")
	var walk func(cur any, idx int)
	walk = func(cur any, idx int) {
		if idx >= len(segs) {
			return
		}
		seg := segs[idx]
		// 处理数组通配符: key[*]
		if strings.HasSuffix(seg, "[*]") {
			key := strings.TrimSuffix(seg, "[*]")
			mm, ok := cur.(map[string]any)
			if !ok {
				return
			}
			arr, ok := mm[key].([]any)
			if !ok {
				return
			}
			for i := range arr {
				walk(arr[i], idx+1)
			}
			return
		}
		// 处理map通配符: *
		if seg == "*" && idx == len(segs)-1 {
			if mm, ok := cur.(map[string]any); ok {
				for k := range mm {
					delete(mm, k)
				}
			}
			return
		}
		// 普通键
		switch node := cur.(type) {
		case map[string]any:
			if idx == len(segs)-1 {
				delete(node, seg)
				return
			}
			next, ok := node[seg]
			if !ok {
				return
			}
			walk(next, idx+1)
		case []any:
			for i := range node {
				walk(node[i], idx)
			}
		}
	}
	walk(m, 0)
}

// keepOnly保留map中指定的字段
func keepOnly(m map[string]any, include []string) map[string]any {
	out := map[string]any{}
	for _, p := range include {
		copyIn(out, m, strings.Split(p, "."))
	}
	return out
}

// copyIn将指定路径的值从src复制到dst
func copyIn(dst map[string]any, src any, segs []string) {
	if len(segs) == 0 {
		return
	}
	switch s := src.(type) {
	case map[string]any:
		key := segs[0]
		if key == "*" {
			for k, v := range s {
				copyIn(ensureMap(dst, k), v, segs[1:])
			}
			return
		}
		v, ok := s[key]
		if !ok {
			return
		}
		if len(segs) == 1 {
			dst[key] = v
			return
		}
		d := ensureMap(dst, key)
		copyIn(d, v, segs[1:])
	case []any:
		key := segs[0]
		if strings.HasSuffix(key, "[*]") {
			if len(segs) == 1 {
				dstArr := make([]any, len(s))
				for i := range s {
					dstArr[i] = s[i]
				}
				dst[strings.TrimSuffix(key, "[*]")] = dstArr
				return
			}
		}
	}
}

// ensureMap确保map中存在指定键的子map
func ensureMap(m map[string]any, k string) map[string]any {
	v, ok := m[k]
	if !ok {
		n := map[string]any{}
		m[k] = n
		return n
	}
	mm, ok := v.(map[string]any)
	if !ok {
		mm = map[string]any{}
		m[k] = mm
	}
	return mm
}

// cleanCommonMeta清理资源的公共元数据
func cleanCommonMeta(m map[string]any) {
	for _, p := range []string{
		"metadata.annotations",
		"metadata.managedFields",
		"metadata.creationTimestamp",
		"metadata.generation",
		"metadata.resourceVersion",
		"metadata.uid",
		"status",
	} {
		deletePath(m, p)
	}
}

// keepOnlyLabelKeys保留map中指定的标签键
func keepOnlyLabelKeys(lbls map[string]any, keep sets.Set[string]) map[string]any {
	out := map[string]any{}
	for k, v := range lbls {
		if keep.Has(k) {
			out[k] = v
		}
	}
	return out
}

// filterDeployment根据规则过滤Deployment资源
func filterDeployment(m map[string]any, rule FieldRule) map[string]any {
	if len(rule.Include) > 0 {
		m = keepOnly(m, rule.Include)
	} else {
		cleanCommonMeta(m)
		for _, p := range rule.Exclude {
			deletePath(m, p)
		}
		if ml, ok := getMap(m, "metadata", "labels"); ok {
			m2 := keepOnlyLabelKeys(ml, sets.New[string]("name", "tenc-projectid"))
			setMap(m, m2, "metadata", "labels")
		}
		if ml, ok := getMap(m, "spec", "template", "metadata", "labels"); ok {
			m2 := keepOnlyLabelKeys(ml, sets.New[string]("name", "tenc-projectid"))
			setMap(m, m2, "spec", "template", "metadata", "labels")
		}
	}
	return m
}

// filterService根据规则过滤Service资源
func filterService(m map[string]any, rule FieldRule) map[string]any {
	if len(rule.Include) > 0 {
		return keepOnly(m, rule.Include)
	}
	cleanCommonMeta(m)
	return keepOnly(m, defaultConfig().Service.Include)
}

// filterConfigMap根据规则过滤ConfigMap资源
func filterConfigMap(m map[string]any, rule FieldRule) map[string]any {
	if len(rule.Include) > 0 {
		return keepOnly(m, rule.Include)
	}
	cleanCommonMeta(m)
	return keepOnly(m, defaultConfig().ConfigMap.Include)
}

// filterRBAC根据规则过滤RBAC资源
func filterRBAC(m map[string]any, rule FieldRule) map[string]any {
	cleanCommonMeta(m)
	if len(rule.Include) > 0 {
		return keepOnly(m, rule.Include)
	}
	return m
}

// getMap从map中获取嵌套的子map
func getMap(m map[string]any, keys ...string) (map[string]any, bool) {
	cur := m
	for _, k := range keys {
		v, ok := cur[k]
		if !ok {
			return nil, false
		}
		mm, ok := v.(map[string]any)
		if !ok {
			return nil, false
		}
		cur = mm
	}
	return cur, true
}

// setMap设置map中嵌套的子map
func setMap(m map[string]any, val map[string]any, keys ...string) {
	cur := m
	for i, k := range keys {
		if i == len(keys)-1 {
			cur[k] = val
			return
		}
		next, ok := cur[k]
		if !ok {
			next = map[string]any{}
			cur[k] = next
		}
		mm, ok := next.(map[string]any)
		if !ok {
			mm = map[string]any{}
			cur[k] = mm
		}
		cur = mm
	}
}

// ----------------------------- DISCOVERY / MATCHING -----------------------------

// serviceMatchesDeployment判断Service是否匹配Deployment
func serviceMatchesDeployment(svc *corev1.Service, dep *appsv1.Deployment) bool {
	if len(svc.Spec.Selector) == 0 {
		return false
	}
	return labels.SelectorFromSet(svc.Spec.Selector).Matches(labels.Set(dep.Spec.Template.Labels))
}

// referencedConfigMaps获取Deployment引用的ConfigMap列表
func referencedConfigMaps(dep *appsv1.Deployment) sets.Set[string] {
	res := sets.New[string]()
	for _, c := range dep.Spec.Template.Spec.Containers {
		for _, env := range c.EnvFrom {
			if env.ConfigMapRef != nil && env.ConfigMapRef.Name != "" {
				res.Insert(env.ConfigMapRef.Name)
			}
		}
		for _, env := range c.Env {
			if env.ValueFrom != nil && env.ValueFrom.ConfigMapKeyRef != nil {
				if n := env.ValueFrom.ConfigMapKeyRef.Name; n != "" {
					res.Insert(n)
				}
			}
		}
	}
	for _, v := range dep.Spec.Template.Spec.Volumes {
		if v.ConfigMap != nil && v.ConfigMap.Name != "" {
			res.Insert(v.ConfigMap.Name)
		}
	}
	return res
}

// ----------------------------- MAIN LOGIC -----------------------------

func main() {
	// 解析命令行参数
	flag.Var(&flagNamespaces, "namespace", "要同步的命名空间（可多次指定）")
	flag.StringVar(&flagKubeconfig, "kubeconfig", clientcmd.RecommendedHomeFile, "kubeconfig 路径")
	flag.StringVar(&flagRoot, "root", ".", "输出根目录（默认当前目录）")
	flag.StringVar(&flagConfigPath, "config", "", "字段同步配置文件（YAML），留空用默认规则")
	flag.Parse()

	// 检查命名空间参数
	if len(flagNamespaces) == 0 {
		fmt.Fprintln(os.Stderr, "必须至少指定一个 --namespace")
		os.Exit(2)
	}

	// 加载配置
	cfg, err := loadConfig(flagConfigPath)
	if err != nil {
		panic(err)
	}

	// 初始化Kubernetes客户端
	cli := mustClient(flagKubeconfig)
	ctx := context.Background()

	// 同步每个命名空间
	for _, ns := range flagNamespaces {
		if err := syncNamespace(ctx, cli, ns, cfg); err != nil {
			fmt.Fprintf(os.Stderr, "[namespace %s] ERROR: %v\n", ns, err)
		}
	}
}

// syncNamespace同步指定命名空间的资源
func syncNamespace(ctx context.Context, cli *kubernetes.Clientset, ns string, cfg *Config) error {
	root := filepath.Join(flagRoot, ns)
	if err := ensureDir(root); err != nil {
		return err
	}

	// 1) 获取Deployment列表
	deps, err := cli.AppsV1().Deployments(ns).List(ctx, metav1.ListOptions{})
	if err != nil {
		return err
	}

	// 预取所有Service（用于匹配selector）
	svcs, err := cli.CoreV1().Services(ns).List(ctx, metav1.ListOptions{})
	if err != nil {
		return err
	}

	// 预取所有RBAC资源
	rbCli := cli.RbacV1()
	roles, _ := rbCli.Roles(ns).List(ctx, metav1.ListOptions{})
	roleBindings, _ := rbCli.RoleBindings(ns).List(ctx, metav1.ListOptions{})
	clusterRoles, _ := rbCli.ClusterRoles().List(ctx, metav1.ListOptions{})
	clusterRoleBindings, _ := rbCli.ClusterRoleBindings().List(ctx, metav1.ListOptions{})

	for _, dep := range deps.Items {
		name := dep.Name
		dir := filepath.Join(root, fmt.Sprintf("deployment-%s", name))
		if err := ensureDir(dir); err != nil {
			return err
		}

		// 合并Deployment和匹配的Service -> <name>.yaml
		var mergedDocs [][]byte
		// Deployment
		if b, err := marshalFiltered(&dep, func(m map[string]any) map[string]any { return filterDeployment(m, cfg.Deployment) }); err == nil {
			mergedDocs = append(mergedDocs, b)
		} else {
			return err
		}
		// 匹配的Service（可能多个，通常一个）
		for i := range svcs.Items {
			svc := &svcs.Items[i]
			if serviceMatchesDeployment(svc, &dep) {
				b, err := marshalFiltered(svc, func(m map[string]any) map[string]any { return filterService(m, cfg.Service) })
				if err != nil {
					return err
				}
				mergedDocs = append(mergedDocs, b)
			}
		}
		if err := writeYAML(filepath.Join(dir, fmt.Sprintf("%s.yaml", name)), mergedDocs); err != nil {
			return err
		}

		// ConfigMap（被引用的） -> configmap.yaml
		cmNames := referencedConfigMaps(&dep).UnsortedList()
		sort.Strings(cmNames)
		if len(cmNames) > 0 {
			var cmDocs [][]byte
			for _, cmn := range cmNames {
				cm, err := cli.CoreV1().ConfigMaps(ns).Get(ctx, cmn, metav1.GetOptions{})
				if err != nil {
					continue
				}
				b, err := marshalFiltered(cm, func(m map[string]any) map[string]any { return filterConfigMap(m, cfg.ConfigMap) })
				if err != nil {
					return err
				}
				cmDocs = append(cmDocs, b)
			}
			if len(cmDocs) > 0 {
				if err := writeYAML(filepath.Join(dir, "configmap.yaml"), cmDocs); err != nil {
					return err
				}
			}
		}

		// RBAC：基于Deployment.spec.template.spec.serviceAccountName 追溯
		if saName := dep.Spec.Template.Spec.ServiceAccountName; saName != "" {
			var rbacDocs [][]byte
			// ServiceAccount（namespaced）
			if sa, err := cli.CoreV1().ServiceAccounts(ns).Get(ctx, saName, metav1.GetOptions{}); err == nil {
				b, err := marshalFiltered(sa, func(m map[string]any) map[string]any { return filterRBAC(m, cfg.RBAC) })
				if err != nil {
					return err
				}
				rbacDocs = append(rbacDocs, b)
			}
			// RoleBinding（包含该 SA 的）
			roleNameSet := sets.New[string]()
			clusterRoleNameSet := sets.New[string]()
			for i := range roleBindings.Items {
				rb := &roleBindings.Items[i]
				for _, subj := range rb.Subjects {
					if subj.Kind == rbacv1.ServiceAccountKind && subj.Name == saName && subj.Namespace == ns {
						// 引用的 Role/ClusterRole 收集
						if rb.RoleRef.Kind == "Role" {
							roleNameSet.Insert(rb.RoleRef.Name)
						}
						if rb.RoleRef.Kind == "ClusterRole" {
							clusterRoleNameSet.Insert(rb.RoleRef.Name)
						}
						b, err := marshalFiltered(rb, func(m map[string]any) map[string]any { return filterRBAC(m, cfg.RBAC) })
						if err != nil {
							return err
						}
						rbacDocs = append(rbacDocs, b)
					}
				}
			}
			// Role
			for i := range roles.Items {
				role := &roles.Items[i]
				if roleNameSet.Has(role.Name) {
					b, err := marshalFiltered(role, func(m map[string]any) map[string]any { return filterRBAC(m, cfg.RBAC) })
					if err != nil {
						return err
					}
					rbacDocs = append(rbacDocs, b)
				}
			}
			// ClusterRole + ClusterRoleBinding（与该 SA 相关的 CRB）
			for i := range clusterRoleBindings.Items {
				crb := &clusterRoleBindings.Items[i]
				match := false
				for _, s := range crb.Subjects {
					if s.Kind == rbacv1.ServiceAccountKind && s.Name == saName && s.Namespace == ns {
						match = true
						break
					}
				}
				if match {
					clusterRoleNameSet.Insert(crb.RoleRef.Name)
					b, err := marshalFiltered(crb, func(m map[string]any) map[string]any { return filterRBAC(m, cfg.RBAC) })
					if err != nil {
						return err
					}
					rbacDocs = append(rbacDocs, b)
				}
			}
			for i := range clusterRoles.Items {
				cr := &clusterRoles.Items[i]
				if clusterRoleNameSet.Has(cr.Name) {
					b, err := marshalFiltered(cr, func(m map[string]any) map[string]any { return filterRBAC(m, cfg.RBAC) })
					if err != nil {
						return err
					}
					rbacDocs = append(rbacDocs, b)
				}
			}
			if len(rbacDocs) > 0 {
				if err := writeYAML(filepath.Join(dir, "rbac.yaml"), rbacDocs); err != nil {
					return err
				}
			}
		}
	}

	// 2) 补充同步其他无Deployment关联的Service/ConfigMap（可按需启用）

	return nil
}

// marshalFiltered将对象序列化为YAML，并根据规则过滤字段
func marshalFiltered(obj runtime.Object, filter func(map[string]any) map[string]any) ([]byte, error) {
	m, err := objToMap(obj)
	if err != nil {
		return nil, err
	}
	m = withGVK(m, obj)
	m = filter(m)
	return mapToYAML(m)
}

// withGVK为map添加GVK信息
func withGVK(m map[string]any, obj runtime.Object) map[string]any {
	gvks := obj.GetObjectKind().GroupVersionKind()
	// 某些typed对象可能未携带GVK，这里兜底
	if gvks.Empty() {
		switch obj.(type) {
		case *appsv1.Deployment:
			gvks = schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}
		case *corev1.Service:
			gvks = schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Service"}
		case *corev1.ConfigMap:
			gvks = schema.GroupVersionKind{Group: "", Version: "v1", Kind: "ConfigMap"}
		case *corev1.ServiceAccount:
			gvks = schema.GroupVersionKind{Group: "", Version: "v1", Kind: "ServiceAccount"}
		case *rbacv1.Role:
			gvks = schema.GroupVersionKind{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "Role"}
		case *rbacv1.RoleBinding:
			gvks = schema.GroupVersionKind{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "RoleBinding"}
		case *rbacv1.ClusterRole:
			gvks = schema.GroupVersionKind{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRole"}
		case *rbacv1.ClusterRoleBinding:
			gvks = schema.GroupVersionKind{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRoleBinding"}
		default:
			// 留空也行
		}
	}
	if gvks.Version != "" {
		if gvks.Group == "" {
			m["apiVersion"] = gvks.Version
		} else {
			m["apiVersion"] = gvks.Group + "/" + gvks.Version
		}
	}
	if gvks.Kind != "" {
		m["kind"] = gvks.Kind
	}
	return m
}

// ----------------------------- UTIL -----------------------------

// exists检查文件或目录是否存在
func exists(path string) bool {
	_, err := os.Stat(path)
	return err == nil
}

// touch创建空文件（若不存在）
func touch(path string) error {
	if exists(path) {
		return nil
	}
	return os.WriteFile(path, []byte{}, fs.FileMode(0o644))
}

// ----------------------------- END -----------------------------
